/**
`useCallback`  是 React 中的一个 Hook，用于优化函数组件的性能。它可以防止在每次渲染时重新创建函数，从而减少不必要的渲染和性能开销。
  1. 基本概念
  - 作用： `useCallback`  返回一个记忆化的回调函数，只有当其依赖项发生变化时，才会更新这个函数。这对于传递回调函数给子组件时非常有用，尤其是在子组件依赖于该函数的情况下。

  2. 使用方式
   基本语法
  const memoizedCallback = useCallback(() => {
    // 你的回调逻辑
  }, [dependencies]);
  - 回调函数：第一个参数是你想要记忆的函数。
  - 依赖数组：第二个参数是一个数组，包含所有依赖项，当这些依赖项发生变化时， `useCallback`  会返回一个新的函数。

   示例：
  import React, { useState, useCallback } from 'react';

  const Counter = () => {
    const [count, setCount] = useState(0);
    
    // 使用 useCallback 来记忆 handleClick 函数
    const handleClick = useCallback(() => {
      setCount(prevCount => prevCount + 1);
    }, []); // 依赖项为空数组，handleClick 不会在每次渲染时变化

    return (
      <div>
        <p>Count: {count}</p>
        <button onClick={handleClick}>Increment</button>
      </div>
    );
  };
  3. 使用场景
  - 性能优化：在将回调函数传递给子组件时，使用  `useCallback`  可以避免子组件因父组件的重新渲染而不必要地重新渲染。
  - 依赖于 props 的回调：当回调函数依赖于组件的 props 或 state 时，可以使用  `useCallback`  来确保函数的稳定性。

  4. 注意事项
  - 不总是需要使用：在小型组件中，使用  `useCallback`  可能会导致额外的复杂性，只有在性能问题明显时才需要考虑使用。
  - 依赖项的管理：确保正确管理依赖项数组中的依赖关系，以避免潜在的 bug。

  5. 总结
  `useCallback`  是一个用于优化性能的 Hook，能够记忆回调函数并防止不必要的重新创建。通过合理使用  `useCallback` ，开发者可以提高 React 应用的性能，尤其是在处理大量渲染和复杂组件树时。理解何时以及如何使用  `useCallback`  是提升 React 应用性能的关键。
 */
import React, { memo, useState, useCallback, useRef } from "react";

// useCallback性能优化的点:
// 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件
// props中的属性发生改变时, 组件本身就会被重新渲染
const Home = memo((props) => {
  const { increment } = props;
  console.log("Home render");

  return (
    <div>
      <button onClick={increment}>increment+1</button>
      {/* 999个子组件 */}
    </div>
  );
});

const App = memo(() => {
  const [count, setCount] = useState(0);
  const [meesage, setMessage] = useState("hello useCallback");

  // 闭包陷阱: useCallback
  // const increment = useCallback(() => {
  //   console.log("increment++");
  //   setCount(count + 1);
  // }, [count]);

  // 进一步的优化: 当count发生改变时, 也使用同一个函数(了解)
  // 做法一: 将count依赖移除掉, 缺点: 闭包陷阱
  // 做法二: useRef, 在组件多次渲染时, 返回的是同一个值
  const countRef = useRef(0);
  countRef.current = count;
  const increment = useCallback(() => {
    console.log("increment++");
    countRef.current++;
    setCount(countRef.current);
  }, []); // 依赖项为空数组，increment 不会在每次渲染时变化

  return (
    <div>
      <h2>count: {count}</h2>
      <button onClick={increment}>increment+1</button>

      <Home increment={increment} />
      <button onClick={(e) => setMessage(Math.random())}>修改message</button>
    </div>
  );
});

export default App;

/**
闭包（Closure）是 JavaScript 中一个重要的概念，它允许一个函数访问其外部作用域的变量。尽管闭包非常强大，但在使用时也可能会遇到一些“闭包陷阱”。
  1. 什么是闭包

  - 定义：闭包是一个函数，它可以“记住”并访问它被创建时的作用域，即使这个函数是在其外部作用域中被调用的。
  - 用法：闭包可以用于数据封装、私有变量、函数工厂等场景。

  示例：
  function outerFunction() {
    let outerVariable = 'I am outside!';

    function innerFunction() {
      console.log(outerVariable); // 访问外部变量
    }

    return innerFunction;
  }

  const closure = outerFunction();
  closure(); // 输出: I am outside!
  2. 闭包陷阱
  尽管闭包非常有用，但在某些情况下，使用不当可能会导致一些问题，尤其是在循环和异步操作中。以下是一些常见的闭包陷阱：
  1. 循环中的闭包陷阱
  在循环中使用闭包时，可能会出现意外的行为，因为闭包会捕获外部变量的引用，而不是变量的值。
  # 示例：
  const funcs = [];

  for (var i = 0; i < 3; i++) {
    funcs[i] = function() {
      console.log(i); // 输出: 3
    };
  }

  funcs[0](); // 3
  funcs[1](); // 3
  funcs[2](); // 3
  解决方法：可以使用  `let`  声明变量， `let`  会为每次循环创建一个新的作用域。
  const funcs = [];

  for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
      console.log(i); // 输出: 0, 1, 2
    };
  }

  funcs[0](); // 0
  funcs[1](); // 1
  funcs[2](); // 2
  2. 异步操作中的闭包陷阱

  在异步操作（如定时器）中，闭包可能会捕获外部变量的最终值，而不是在创建闭包时的值。

  # 示例：
  const arr = [];

  for (var i = 0; i < 3; i++) {
    setTimeout(function() {
      console.log(i); // 输出: 3, 3, 3
    }, 1000);
  }
  解决方法：可以使用立即调用的函数表达式（IIFE）来创建一个新的作用域。
  for (var i = 0; i < 3; i++) {
    (function(i) {
      setTimeout(function() {
        console.log(i); // 输出: 0, 1, 2
      }, 1000);
    })(i);
  }
  3. 总结
  - 闭包 是一个强大的特性，允许函数访问其外部作用域的变量。
  - 使用闭包时要注意可能出现的陷阱，尤其是在循环和异步操作中。
  - 理解闭包的作用域链和变量捕获机制可以帮助开发者避免常见的错误，提高代码的可维护性和可读性。
 */
